
#include "etl_calendar.h"
#include "etl_string.h"

#include <string.h>

using namespace etl;

etl_calendar::etl_calendar() :
  epoch_time( time(0) )
{
  // breaking down, and building up time this initial time causes our time
  // to incorporate any local timezone and DST settings.
  struct tm bdt;
  break_down( &bdt );
  build_up( &bdt );
}

etl_calendar::etl_calendar( time_t then ) :
  epoch_time( then )
{
  // breaking down, and building up time this initial time causes our time
  // to incorporate any local timezone and DST settings.
  struct tm bdt;
  break_down( &bdt );
  build_up( &bdt );
}

etl_calendar::etl_calendar( struct tm* bdt ) :
  epoch_time( 0 )
{
  if( bdt )
    {
      build_up( bdt );
    }
}

etl_calendar::etl_calendar(etl_string str, etl_string format) :
  epoch_time( 0 )
{
  struct tm bdt;
  break_down(&bdt);
  bdt.tm_mon = 0;
  bdt.tm_mday = 1;
  bdt.tm_year = 70;
  bdt.tm_hour = 0;
  bdt.tm_min = 0;
  bdt.tm_sec = 0;
  strptime(str.c_str(), format.c_str(), &bdt);
  build_up(&bdt);
}

etl_calendar::etl_calendar( const etl_calendar& obj )
{
  copy( obj );
}

etl_calendar::~etl_calendar()
{
}

etl_calendar& etl_calendar::operator = ( const etl_calendar& obj )
{
  return copy( obj );
}

etl_calendar& etl_calendar::operator = ( const time_t& then )
{
  epoch_time = then;
  return *this;
}

bool etl_calendar::operator == ( const etl_calendar& obj )
{
  if( epoch_time == obj.epoch_time )
    return true;
  return false;
}

bool etl_calendar::operator < ( const etl_calendar& obj )
{
  if( epoch_time < obj.epoch_time )
    return true;
  return false;
}

bool etl_calendar::operator <= ( const etl_calendar& obj )
{
  if( epoch_time <= obj.epoch_time )
    return true;
  return false;
}

bool etl_calendar::operator > ( const etl_calendar& obj )
{
  if( epoch_time > obj.epoch_time )
    return true;
  return false;
}

bool etl_calendar::operator >= ( const etl_calendar& obj )
{
  if( epoch_time >= obj.epoch_time )
    return true;
  return false;
}

int etl_calendar::get_component_max_value( etl_calendar_components type )
{
  struct tm bdt;
  break_down( &bdt );

  switch( type )
    {
    case SECOND:
      return 59;
      break;
    case MINUTE:
      return 59;
      break;
    case HOUR:
      return 23;
      break;
    case MONTH_DAY:
      switch( bdt.tm_mon )
	{
	case 0:
	  return 31;
	  break;
	case 1:
	  if( (bdt.tm_year % 4) == 0 )
	    return 29;
	  else return 28;
	  break;
	case 2:
	  return 31;
	  break;
	case 3:
	  return 30;
	  break;
	case 4:
	  return 31;
	  break;
	case 5:
	  return 30;
	  break;
	case 6:
	  return 31;
	  break;
	case 7:
	  return 31;
	  break;
	case 8:
	  return 30;
	  break;
	case 9:
	  return 31;
	  break;
	case 10:
	  return 30;
	  break;
	case 11:
	  return 31;
	  break;
	default:
	  return -1;
	  break;
	};
      break;
    case MONTH_WEEK:
      return 5;
      break;
    case MONTH:
      return 11;
      break;
    case YEAR:
      return 2038;
      break;
    case YEAR_WEEK:
      return 53;
      break;
    case WEEK_DAY:
      return 6;
      break;
    case YEAR_DAY:
      if( (bdt.tm_year % 4) == 0 )
	return 366;
      else return 365;
      break;

    default:
      break;
    }

  return -1;
}

int etl_calendar::inc( etl_calendar_components type, int interval )
{
  struct tm bdt;

  break_down( &bdt );

  switch( type )
    {
    case SECOND:
      bdt.tm_sec += interval;
      break;
    case MINUTE:
      bdt.tm_min += interval;
      break;
    case HOUR:
      bdt.tm_hour += interval;
      break;
    case MONTH_DAY:
      bdt.tm_mday += interval;
      break;
    case MONTH_WEEK:
      bdt.tm_mday += (interval*7);
      break;
    case MONTH:
      bdt.tm_mon += interval;
      break;
    case YEAR:
      bdt.tm_year += interval;
      break;
    case YEAR_WEEK:
      bdt.tm_mday += (interval*7);
      break;
    case WEEK_DAY:
      bdt.tm_mday += interval;
      break;
    case YEAR_DAY:
      bdt.tm_mday += interval;
      break;
    default:
      break;
    };

  build_up( &bdt );

  return 0;
}

int etl_calendar::dec( etl_calendar_components type, int interval )
{
  struct tm bdt;

  break_down( &bdt );

  switch( type )
    {
    case SECOND:
      bdt.tm_sec -= interval;
      break;
    case MINUTE:
      bdt.tm_min -= interval;
      break;
    case HOUR:
      bdt.tm_hour -= interval;
      break;
    case MONTH_DAY:
      bdt.tm_mday -= interval;
      break;
    case MONTH_WEEK:
      bdt.tm_mday -= (interval*7);
      break;
    case MONTH:
      bdt.tm_mon -= interval;
      break;
    case YEAR:
      bdt.tm_year -= interval;
      break;
    case YEAR_WEEK:
      bdt.tm_mday -= (interval*7);
      break;
    case WEEK_DAY:
      bdt.tm_mday -= interval;
      break;
    case YEAR_DAY:
      bdt.tm_mday -= interval;
      break;
    default:
      break;
    };

  build_up( &bdt );

  return 0;
}

int etl_calendar::get_component( etl_calendar_components type )
{
  struct tm bdt;

  break_down( &bdt );
  
  switch( type )
    {
    case SECOND:
      return bdt.tm_sec;
      break;     
    case MINUTE:
      return bdt.tm_min;
      break;
    case HOUR:
      return bdt.tm_hour;
      break;
    case MONTH_DAY:
      return bdt.tm_mday;
      break;
    case MONTH:
      return bdt.tm_mon;
      break;
    case YEAR:
      return bdt.tm_year;
      break;
    case YEAR_WEEK:
      {
	char week_num_string[8];
	strftime( week_num_string, 8, "%U", &bdt );
	etl_string week_num = (char*)week_num_string;
	return (int)week_num.to_long();
      }
      break;
    case WEEK_DAY:
      return bdt.tm_wday;
      break;
    case YEAR_DAY:
      return bdt.tm_yday;
      break;
    default:
      break;
    }

  return -1;
}

int etl_calendar::set_component( etl_calendar_components type, int val )
{
  struct tm bdt;

  break_down( &bdt );

  switch( type )
    {
    case SECOND:
      bdt.tm_sec = val;
      break;
    case MINUTE:
      bdt.tm_min = val;
      break;
    case HOUR:
      bdt.tm_hour = val;
      break;
    case MONTH_DAY:
      bdt.tm_mday = val;
      break;
    case MONTH_WEEK:
      return -1;
      break;
    case MONTH:
      bdt.tm_mon = val;
      break;
    case YEAR:
      bdt.tm_year = val;
      break;
    case YEAR_WEEK:
      return -1;
      break;
    case WEEK_DAY:
      return -1;
      break;
    case YEAR_DAY:
      return -1;
      break;
    default:
      break;
    };

  build_up( &bdt );

  return 0;
}

time_t etl_calendar::get_epoch_time()
{
  return epoch_time;
}

etl_calendar& etl_calendar::copy( const etl_calendar& obj )
{
  this->epoch_time = obj.epoch_time;
  return *this;
}

int etl_calendar::break_down( struct tm* bdt )
{
  return ( bdt && localtime_r( &epoch_time, bdt ) ) ? 0 : -1;
}

int etl_calendar::break_down( struct tm* bdt, time_t then )
{
  return ( bdt && localtime_r( &then, bdt ) ) ? 0 : -1;
}

int etl_calendar::build_up( struct tm* bdt )
{
  if( bdt )
    {
      // tm_isdst = 1 causes mktime() to "discover" the proper TZ setting.
      bdt->tm_isdst = -1;
      epoch_time = mktime( bdt );
      return 0;
    }

  return -1;
}

int etl_calendar::build_up( struct tm* bdt, time_t* then )
{
  if( bdt && then )
    {
      // tm_isdst = 1 causes mktime() to "discover" the proper TZ setting.
      bdt->tm_isdst = -1;
      *then = mktime( bdt );
      return 0;
    }
  return -1;
}
